"""Browse media features for media player."""

from __future__ import annotations

from collections.abc import Sequence
from dataclasses import dataclass, field
from datetime import timedelta
import logging
from typing import Any
from urllib.parse import quote

import yarl

from homeassistant.components.http.auth import async_sign_path
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.network import (
    NoURLAvailableError,
    get_supervisor_network_url,
    get_url,
    is_hass_url,
)

from .const import CONTENT_AUTH_EXPIRY_TIME, MediaClass, MediaType

# Paths that we don't need to sign
PATHS_WITHOUT_AUTH = (
    "/api/tts_proxy/",
    "/api/esphome/ffmpeg_proxy/",
    "/api/assist_satellite/static/",
)


@callback
def async_process_play_media_url(
    hass: HomeAssistant,
    media_content_id: str,
    *,
    allow_relative_url: bool = False,
    for_supervisor_network: bool = False,
) -> str:
    """Update a media URL with authentication if it points at Home Assistant."""
    parsed = yarl.URL(media_content_id)

    if parsed.scheme and parsed.scheme not in ("http", "https"):
        return media_content_id

    if parsed.is_absolute():
        if not is_hass_url(hass, media_content_id):
            return media_content_id
    elif media_content_id[0] != "/":
        return media_content_id

    # https://github.com/pylint-dev/pylint/issues/3484
    # pylint: disable-next=using-constant-test
    if parsed.query:
        logging.getLogger(__name__).debug(
            "Not signing path for content with query param"
        )
    elif parsed.path.startswith(PATHS_WITHOUT_AUTH):
        # We don't sign this path if it doesn't need auth. Although signing itself can't
        # hurt, some devices are unable to handle long URLs and the auth signature might
        # push it over.
        pass
    else:
        signed_path = async_sign_path(
            hass,
            quote(parsed.path),
            timedelta(seconds=CONTENT_AUTH_EXPIRY_TIME),
        )
        media_content_id = str(parsed.join(yarl.URL(signed_path)))

    # convert relative URL to absolute URL
    if not parsed.is_absolute() and not allow_relative_url:
        base_url = None
        if for_supervisor_network:
            base_url = get_supervisor_network_url(hass)

        if not base_url:
            try:
                base_url = get_url(hass)
            except NoURLAvailableError as err:
                msg = "Unable to determine Home Assistant URL to send to device"
                if (
                    hass.config.api
                    and hass.config.api.use_ssl
                    and (not hass.config.external_url or not hass.config.internal_url)
                ):
                    msg += ". Configure internal and external URL in general settings."
                raise HomeAssistantError(msg) from err

        media_content_id = f"{base_url}{media_content_id}"

    return media_content_id


class BrowseMedia:
    """Represent a browsable media file."""

    def __init__(
        self,
        *,
        media_class: MediaClass | str,
        media_content_id: str,
        media_content_type: MediaType | str,
        title: str,
        can_play: bool,
        can_expand: bool,
        children: Sequence[BrowseMedia] | None = None,
        children_media_class: MediaClass | str | None = None,
        thumbnail: str | None = None,
        not_shown: int = 0,
        can_search: bool = False,
    ) -> None:
        """Initialize browse media item."""
        self.media_class = media_class
        self.media_content_id = media_content_id
        self.media_content_type = media_content_type
        self.title = title
        self.can_play = can_play
        self.can_expand = can_expand
        self.children = children
        self.children_media_class = children_media_class
        self.thumbnail = thumbnail
        self.not_shown = not_shown
        self.can_search = can_search

    def as_dict(self, *, parent: bool = True) -> dict[str, Any]:
        """Convert Media class to browse media dictionary."""
        if self.children_media_class is None and self.children:
            self.calculate_children_class()

        response: dict[str, Any] = {
            "title": self.title,
            "media_class": self.media_class,
            "media_content_type": self.media_content_type,
            "media_content_id": self.media_content_id,
            "children_media_class": self.children_media_class,
            "can_play": self.can_play,
            "can_expand": self.can_expand,
            "can_search": self.can_search,
            "thumbnail": self.thumbnail,
        }

        if not parent:
            return response

        response["not_shown"] = self.not_shown

        if self.children:
            response["children"] = [
                child.as_dict(parent=False) for child in self.children
            ]
        else:
            response["children"] = []

        return response

    def calculate_children_class(self) -> None:
        """Count the children media classes and calculate the correct class."""
        self.children_media_class = MediaClass.DIRECTORY
        assert self.children is not None
        proposed_class = self.children[0].media_class
        if all(child.media_class == proposed_class for child in self.children):
            self.children_media_class = proposed_class

    def __repr__(self) -> str:
        """Return representation of browse media."""
        return f"<BrowseMedia {self.title} ({self.media_class})>"


@dataclass(kw_only=True, frozen=True)
class SearchMedia:
    """Represent search results."""

    version: int = field(default=1)
    result: list[BrowseMedia]

    def as_dict(self, *, parent: bool = True) -> dict[str, Any]:
        """Convert SearchMedia class to browse media dictionary."""
        return {
            "result": [item.as_dict(parent=parent) for item in self.result],
        }


@dataclass(kw_only=True, frozen=True)
class SearchMediaQuery:
    """Represent a search media file."""

    search_query: str
    media_content_type: MediaType | str | None = field(default=None)
    media_content_id: str | None = None
    media_filter_classes: list[MediaClass] | None = field(default=None)
